package nba2kmodtool.filedesc;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import nba2kmodtool.util.BufferUtil;
import nba2kmodtool.util.ByteFileReader;

/**
 * NBA2K Game Model Part Reader
 * @company ZSword (C) Copyright
 * @author JemiZhuu(周士淳)
 * @category N2kModelPartReader
 * @version 1.0
 * @since 2011-11-16
 */
public class N2kModelPartReader {
	protected static final boolean IS_DEBUG = BaseIffFile.IS_DEBUG;
	static final float COORD_MOD = 32767F;
	protected static final float MOD = COORD_MOD;
	static final float COORD_BYTE_MOD = 127F;
	protected static final float BYTE_MOD = 127F;

	protected BaseIffFile iff;
	protected long fileType;
	protected File file;
	protected File cdfFile;

	protected N2kModelPartReader(BaseIffFile iff) {
		this.iff = iff;
		this.fileType = iff.fileType;
		this.file = iff.file;
		this.cdfFile = iff.cdfFile;
	}

	/**
	 * 解析3D模型顶点数据
	 * @param model
	 * @param in
	 * @return
	 * @throws IOException
	 */
	public List<FloatBuffer> resolve3DModelVertexData(N2k3DModelPart model, ByteFileReader in) throws IOException {
		return resolve3DModelVertexData(model, in, true);
	}

	//Resolve Vertex Data
	private List<FloatBuffer> resolve3DModelVertexData(N2k3DModelPart model, ByteFileReader in, boolean resolveChild)
			throws IOException {
		int vertexNum = model.getVertexNum();
		int perVertexSize = model.getPerVertexSize();
		int perUVNSize = model.getPerUVNSize();
		//int modelVersion = model.getModelVersion();
		//int modelDef1 = model.getModelDef1();
		//int modelDef2 = model.getModelDef2();
		//get vertex position data buffer		
		in.seek(model.getVertexDataStart());
		if (IS_DEBUG) {
			System.out.printf("ModelVertexStart: %#X\n", in.getFilePointer());
		}
		ByteBuffer vBuf = ByteBuffer.allocate(vertexNum * perVertexSize);
		in.read(vBuf.array());
		vBuf.rewind();
		//resolve vertex data
		List<FloatBuffer> resultList = new ArrayList<FloatBuffer>();
		FloatBuffer tempVBuf = FloatBuffer.allocate(vertexNum * 3);
		FloatBuffer tempUVNBuf = FloatBuffer.allocate(vertexNum * 2);
		int dataChunkCount = model.getDataChunkCount();
		if (IS_DEBUG) {
			System.out.printf("ModelType: datacount: %d, vnum: %d - vsize: %d, usize: %d\n", dataChunkCount, vertexNum,
					perVertexSize, perUVNSize);
		}
		boolean isMod = false;
		vBuf.position(6);
		int modFlag = BufferUtil.readBytesAsInt(vBuf, 2);
		if (MOD == modFlag) {
			isMod = true;
			if (IS_DEBUG) {
				System.out.println("IsMod:" + modFlag);
			}
		}
		vBuf.position(0);
		if (dataChunkCount == 1) {
			//data chunk=1
			//get vertex data
			for (int i = 0; i < vertexNum; i++) {
				vBuf.position(i * perVertexSize);
				float[] coordinate = this.readVertexCoordinate(vBuf, perVertexSize, isMod);
				tempVBuf.put(coordinate[0]);
				tempVBuf.put(coordinate[1]);
				tempVBuf.put(coordinate[2]);
				float[] uv = this.readVertexUVCoordinate(vBuf, perVertexSize, isMod);
				tempUVNBuf.put(uv[0]);
				tempUVNBuf.put(uv[1]);
			}
		} else {
			//data chunk>1
			//resolve vertex position
			for (int i = 0; i < vertexNum; i++) {
				vBuf.position(i * perVertexSize);
				float[] coordinate = this.readVertexCoordinate(vBuf, perVertexSize, isMod);
				tempVBuf.put(coordinate[0]);
				tempVBuf.put(coordinate[1]);
				tempVBuf.put(coordinate[2]);
			}
			//get uvn position data buffer
			in.seek(model.getUvnDataStart());
			if (IS_DEBUG) {
				System.out.printf("ModelUvDataStart: %#X\n", in.getFilePointer());
			}
			ByteBuffer uvnBuf = ByteBuffer.allocate(vertexNum * perUVNSize);
			in.read(uvnBuf.array());
			uvnBuf.rewind();
			//resolve UV and normal data
			for (int i = 0; i < vertexNum; i++) {
				uvnBuf.position(i * perUVNSize);
				float[] uv = this.readMultiChunkVertexUVCoordinate(uvnBuf, perVertexSize, perUVNSize, isMod);
				tempUVNBuf.put(uv[0]);
				tempUVNBuf.put(uv[1]);
			}
		}
		if (!resolveChild) {
			tempVBuf.rewind();
			tempUVNBuf.rewind();
			resultList.add(tempVBuf);
			resultList.add(tempUVNBuf);
			return resultList;
		}
		N2k3DChildModelPart[] childParts = model.getChildParts();
		//resolve child vertex data
		for (int i = 0; i < childParts.length; i++) {
			N2k3DChildModelPart child = childParts[i];
			int childVertStart = child.getVertStart();
			int childVertEnd = child.getVertNum() + childVertStart;
			FloatBuffer vertexs = FloatBuffer.allocate((childVertEnd - childVertStart) * 3);
			FloatBuffer uvns = FloatBuffer.allocate((childVertEnd - childVertStart) * 2);
			for (int j = childVertStart; j < childVertEnd; j++) {
				vertexs.put(tempVBuf.get(j * 3));
				vertexs.put(tempVBuf.get(j * 3 + 1));
				vertexs.put(tempVBuf.get(j * 3 + 2));
				uvns.put(tempUVNBuf.get(j * 2));
				uvns.put(tempUVNBuf.get(j * 2 + 1));
			}
			vertexs.rewind();
			uvns.rewind();
			child.setVertexCoords(vertexs);
			child.setUvCoords(uvns);
			resultList.add(vertexs);
			resultList.add(uvns);
		}
		return resultList;
	}

	/**
	 * Read Vertex Coordinate
	 * @param buffer
	 * @param perVertexSize
	 * @param isMod
	 * @return
	 */
	protected float[] readVertexCoordinate(ByteBuffer buffer, int perVertexSize, boolean isMod) {
		float x = 0, y = 0, z = 0;
		if (isMod) {
			x = BufferUtil.readBytesAsShort(buffer, 2);
			y = BufferUtil.readBytesAsShort(buffer, 2);
			z = BufferUtil.readBytesAsShort(buffer, 2);
			x /= MOD;
			y /= MOD;
			z /= MOD;
		} else {
			switch (perVertexSize) {
			case 4:
				x = buffer.get();
				y = buffer.get();
				z = buffer.get();
				x /= BYTE_MOD;
				y /= BYTE_MOD;
				z /= BYTE_MOD;
				break;
			default:
				x = BufferUtil.readFloat(buffer);
				y = BufferUtil.readFloat(buffer);
				z = BufferUtil.readFloat(buffer);
			}
		}
		//System.out.println(x + "," + y + "," + z);
		return new float[] { x, y, z };
	}

	/**
	 * Read Vertex UV from Multi Chunk Data Model
	 * @param buffer
	 * @param perVertexSize
	 * @param perUVNSize
	 * @param isMod
	 * @return
	 * @throws IOException
	 */
	protected float[] readMultiChunkVertexUVCoordinate(ByteBuffer buffer, int perVertexSize, int perUVNSize,
			boolean isMod) throws IOException {
		float u = 0, v = 0;
		switch (perUVNSize) {
		case 40:
			buffer.position(buffer.position() + 0x18);
			u = BufferUtil.readFloat(buffer);
			v = BufferUtil.readFloat(buffer);
			break;
		case 36:
			buffer.position(buffer.position() + 0x18);
			break;
		case 28:
			buffer.position(buffer.position() + 0x10);
			break;
		case 24:
			break;
		case 16:
			buffer.position(buffer.position() + 0x04);
		case 4:
			break;
		default:
			throw new IOException("Unkown UVN data Type! " + perUVNSize);
		}
		u = BufferUtil.readBytesAsShort(buffer, 2);
		v = BufferUtil.readBytesAsShort(buffer, 2);
		u /= MOD;
		v = 1F - (v / MOD);
		//System.out.println(u + ", " + v);
		return new float[] { u, v };
	}

	/**
	 * Read Vertex UV Data from Single Chunk Data Model
	 * @param buffer
	 * @param perVertexSize
	 * @param isMod
	 * @return
	 * @throws IOException
	 */
	protected float[] readVertexUVCoordinate(ByteBuffer buffer, int perVertexSize, boolean isMod) throws IOException {
		float u = 0, v = 0;
		switch (perVertexSize) {
		case 56:
			buffer.position(buffer.position() + 0x4);
			break;
		case 52:
			buffer.position(buffer.position() + 0x18);
			u = BufferUtil.readFloat(buffer);
			v = BufferUtil.readFloat(buffer);
			break;
		case 48:
			buffer.position(buffer.position() + 0x18);
			u = BufferUtil.readBytesAsInt(buffer, 2);
			v = BufferUtil.readBytesAsInt(buffer, 2);
			u /= MOD;
			v = 1F - (v / MOD);
			break;
		case 42:
			buffer.position(buffer.position() + 0x04);
			break;
		case 36:
			if (isMod) {
				buffer.position(buffer.position() + 0x18);
				u = BufferUtil.readBytesAsInt(buffer, 2);
				v = BufferUtil.readBytesAsInt(buffer, 2);
				u /= MOD;
				v = 1F - (v / MOD);
				break;
			} else {
				buffer.position(buffer.position() + 8);
				u = BufferUtil.readFloat(buffer);
				v = BufferUtil.readFloat(buffer);
			}
			break;
		case 28:
			u = BufferUtil.readBytesAsInt(buffer, 2);
			v = BufferUtil.readBytesAsInt(buffer, 2);
			u /= MOD;
			v = 1F - (v / MOD);
			break;
		case 4:
			break;
		default:
			throw new IOException("Unkown Vertex data Type! " + perVertexSize);
		}
		return new float[] { u, v };
	}

	/**
	 * Resolve Model Faces Data
	 * @param model
	 * @param in
	 * @return
	 * @throws IOException
	 */
	public List<Integer> resolveIg3DModelFaceData(N2k3DModelPart model, ByteFileReader in) throws IOException {
		in.seek(model.faceDataStart);
		ByteBuffer faceBuf = ByteBuffer.allocate(model.faceDataSize * 2);
		in.read(faceBuf.array());
		faceBuf.rewind();
		List<Integer> coordIdx = new ArrayList<Integer>();
		for (int i = 0; i < model.childParts.length; i++) {
			N2k3DChildModelPart child = model.childParts[i];
			int faceEnd = child.indicesNum0;
			int base = 0;
			int start0 = faceBuf.position();
			faceBuf.rewind();
			faceBuf.position(child.index * 2);
			int a = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
			int b = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
			int c = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
			int invalidLen = 0;
			int g = 3;
			while (a == b | a == c | c == b) {
				a = b;
				b = c;
				c = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
				invalidLen++;
				g++;
			}
			coordIdx.add(a);
			coordIdx.add(b);
			coordIdx.add(c);
			int start = 3 + invalidLen;
			int end = faceEnd;
			for (int j = start; j < end; j++) {
				a = b;
				b = c;
				c = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
				g++;
				if (a != b & a != c & b != c) {
					if (j % 2 == 0) {
						coordIdx.add(a);
						coordIdx.add(b);
						coordIdx.add(c);
					} else {
						coordIdx.add(c);
						coordIdx.add(b);
						coordIdx.add(a);
					}
				}
			}
		}
		return coordIdx;
	}

	/**
	 * Decompress Model Faces data
	 * @param buffer
	 * @return
	 * @throws IOException
	 */
	static IntBuffer decompressModelFaceDatas(ByteBuffer buffer) throws IOException {
		List<Integer> faceDatas = new ArrayList<Integer>();
		int p1 = 0, p2 = 0, p3 = 0;
		p1 = BufferUtil.readBytesAsInt(buffer, 2);
		p2 = BufferUtil.readBytesAsInt(buffer, 2);
		for (int i = 2; i < buffer.capacity() / 2; i++) {
			if (0 == i % 2) {
				p3 = BufferUtil.readBytesAsInt(buffer, 2);
				if (p3 != p1 && p3 != p2 && p1 != p2) {
					faceDatas.add(p3);
					faceDatas.add(p2);
					faceDatas.add(p1);
				}
				p1 = p2;
				p2 = p3;
			} else {
				p3 = BufferUtil.readBytesAsInt(buffer, 2);
				if (p3 != p1 && p3 != p2 && p1 != p2) {
					faceDatas.add(p1);
					faceDatas.add(p2);
					faceDatas.add(p3);
				}
				p1 = p2;
				p2 = p3;
			}
		}
		int faceNum = faceDatas.size() / 3;
		IntBuffer facesBuffer = BufferUtil.allocateIntBufferDirect(faceDatas.size());
		for (int i = 0; i < faceDatas.size(); i++) {
			facesBuffer.put(((Integer) faceDatas.get(i)).intValue());
		}
		return facesBuffer;
	}

	/**	 
	 * Resolve Face Data(needBase whether need sub BaseIndex for child model--if export to .OBJ file, needBase=false)
	 * @param model
	 * @param in
	 * @param needBase
	 * @return
	 * @throws IOException
	 */
	List<IntBuffer> resolve3DModelFaceData(N2k3DModelPart model, ByteFileReader in, boolean... needBase)
			throws IOException {
		List<IntBuffer> resultList = new ArrayList<IntBuffer>();
		//read face data
		in.seek(model.faceDataStart);
		ByteBuffer faceBuf = ByteBuffer.allocate(model.faceDataSize * 2);
		in.read(faceBuf.array());
		for (int i = 0; i < model.childParts.length; i++) {
			N2k3DChildModelPart child = model.childParts[i];
			List<Integer> coordIdx = new ArrayList<Integer>();
			int faceEnd = child.indicesNum0;
			if (child.vertNum == 0) {
				continue;
			}
			int base = child.vertStart;
			if (needBase.length > 0) {
				base = 0;
			}
			int start0 = faceBuf.position();
			faceBuf.rewind();
			faceBuf.position(child.index * 2);
			int a = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
			int b = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
			int c = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
			int invalidLen = 0;
			int g = 3;
			while (a == b | a == c | c == b) {
				a = b;
				b = c;
				c = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
				invalidLen++;
				g++;
			}
			coordIdx.add(a);
			coordIdx.add(b);
			coordIdx.add(c);
			int start = 3 + invalidLen;
			int end = faceEnd;
			for (int j = start; j < end; j++) {
				a = b;
				b = c;
				c = BufferUtil.readBytesAsInt(faceBuf, 2) - base;
				g++;
				if (a != b & a != c & b != c) {
					if (j % 2 == 0) {
						coordIdx.add(a);
						coordIdx.add(b);
						coordIdx.add(c);
					} else {
						coordIdx.add(c);
						coordIdx.add(b);
						coordIdx.add(a);
					}
				}
			}
			//System.out.println("start: " + start0 + ", " + faceBuf.position() + ", " + g * 2 + ", " + invalidLen);
			IntBuffer indices = IntBuffer.allocate(coordIdx.size());
			for (int j = 0; j < coordIdx.size(); j++) {
				indices.put((Integer) coordIdx.get(j));
			}
			indices.rewind();
			child.coordsIndex = indices;
			resultList.add(indices);
		}
		return resultList;
	}
}
